/* 
 * Copyright (c) 2009, Giora Kosoi giorak@gmail.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Giora Kosoi ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Giora Kosoi BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <event.h>

#include <sparrow/errors.h>
#include <sparrow/reactor.h>
#include <sparrow/libevent_reactor_impl.h>

#include <assert.h>

LibeventReactorImpl::LibeventReactorImpl()
    :   eb_(0),
        current_token_(0),
        owner_(0)
{
    eb_ = event_base_new();
}

LibeventReactorImpl::~LibeventReactorImpl()
{
    event_base_free(eb_);
}

void LibeventReactorImpl::set_owner(Reactor *r)
{
    owner_ = r;
}

static void del_event(void *p)
{
    free(p);
}

void LibeventReactorImpl::input_cb(int fd, short e, void *arg)
{
    EventWrapper *ew = (EventWrapper *)arg;
    ew->h->handle_input(fd, ew->token);
}

void LibeventReactorImpl::register_input_handler(int fd, Handler *h, uint64_t token)
{
    EventWrapper *ew = (EventWrapper *)malloc(sizeof(EventWrapper));
    ew->h = h;
    ew->token = token;
    event_assign((event *)ew, eb_, fd, EV_READ | EV_PERSIST, input_cb, ew);

    int result = event_add((event *)ew, 0);
    if(result != 0)
    {
        free(ew);
        throw_error<std::runtime_error>(__FILE__, __LINE__, 
                "event_add failed for input Handler with token [%lld]",
                token);
    }
    inputs_.set(IntKey(token), new VariantPtr(ew, del_event, true));
}

void LibeventReactorImpl::output_cb(int fd, short e, void *arg)
{
    EventWrapper *ew = (EventWrapper *)arg;
    ew->h->handle_output(fd, ew->token);
}

void LibeventReactorImpl::register_output_handler(int fd, Handler *h, uint64_t token)
{
    EventWrapper *ew = (EventWrapper *)malloc(sizeof(EventWrapper));
    ew->h = h;
    ew->token = token;
    event_assign((event *)ew, eb_, fd, EV_WRITE | EV_PERSIST, output_cb, ew); 

    int result = event_add((event *)ew, 0);
    if(result != 0)
    {
        free(ew);
        throw_error<std::runtime_error>(__FILE__, __LINE__, 
                "event_add failed for output Handler with token [%lld]",
                token);
    }
    outputs_.set(IntKey(token), new VariantPtr(ew, del_event, true));
}

void LibeventReactorImpl::error_cb(int fd, short e, void *arg)
{
}

void LibeventReactorImpl::register_error_handler(int fd, Handler *h, uint64_t token)
{
}

void LibeventReactorImpl::timeout_cb(int fd, short e, void *arg)
{
    EventWrapper *ew = (EventWrapper *)arg;
    ew->h->handle_timeout(ew->token);
}

void LibeventReactorImpl::schedule_timer(Handler *h, const timespec &t, uint64_t token)
{
    EventWrapper *ew = (EventWrapper *)malloc(sizeof(EventWrapper));
    ew->h = h;
    ew->token = token;

    timeval tv;
    tv.tv_sec = t.tv_sec;
    tv.tv_usec = t.tv_nsec / 1000;
    int result = event_assign((event *)ew, eb_, -1, EV_PERSIST, timeout_cb, ew);
    result = evtimer_add((event *)ew, &tv);
    if(result != 0)
    {
        free(ew);
        throw_error<std::runtime_error>(__FILE__, __LINE__, 
                "event_add failed for timeout Handler with token [%lld]",
                token);
    }
    timeouts_.set(IntKey(token), new VariantPtr(ew, del_event, true));
 }

void LibeventReactorImpl::unregister_input_handler(uint64_t token)
{
    Variant *v = inputs_.get(IntKey(token), std::nothrow);
    if(v)
    {
        EventWrapper *ew = (EventWrapper *)v->ptr();
        event_del((event *)ew);
        inputs_.remove(IntKey(token));
    }
}

void LibeventReactorImpl::unregister_output_handler(uint64_t token)
{
    Variant *v = outputs_.get(IntKey(token), std::nothrow);
    if(v)
    {
        EventWrapper *ew = (EventWrapper *)v->ptr();
        event_del((event *)ew);
        outputs_.remove(IntKey(token));
    }
}

void LibeventReactorImpl::unregister_error_handler(uint64_t token)
{
}

long LibeventReactorImpl::cancel_timer(uint64_t token)
{
    Variant *v = timeouts_.get(IntKey(token), std::nothrow);
    if(v)
    {
        EventWrapper *ew = (EventWrapper *)v->ptr();
        evtimer_del((event *)ew);
        timeouts_.remove(IntKey(token));
    }
}

uint64_t LibeventReactorImpl::create_token()
{
    return ++current_token_;
}

VariantSet *LibeventReactorImpl::registry()
{
    return &reg_;
}

void LibeventReactorImpl::start()
{
    int result = pthread_create(&th_, 0, start_thread, (void *)this);
}

void *LibeventReactorImpl::start_thread(void *arg)
{
    LibeventReactorImpl *impl = (LibeventReactorImpl *)arg;
    impl->run();
    return NULL;
}

void LibeventReactorImpl::run()
{
    while(true)
    {
        owner_->call_once();
        event_base_loop(eb_, EVLOOP_ONCE);
    }
}

